﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Engine.Dtd.Schema;
using System.Diagnostics;

namespace Engine.Sgml {
    /// <summary>
    /// Remember:
    /// Windows uses:   carriage return - line feed.
    /// UNIX uses:      line feed.
    /// Macintosh uses: carriage return.
    /// </summary>
    internal class SgmlTextFormatWriter : SgmlTextWriter {
        private int depth;
        private int indent;

        public SgmlTextFormatWriter()
            : base() {
            indent = 2;
            }

        public SgmlTextFormatWriter(TextWriter writer)
            : base(writer) {
            indent = 2;
            }

        protected override void WriteCData(string data) {
            if (data == null)
                return;
            int startIndex = IndexOfFirstLineFeed(data);
            if (startIndex < 0)
                startIndex = 0;
            int length = IndexOfEndLineFeed(data);
            if (length < 0)
                length = data.Length;
            for (int i = startIndex; i < length; i++) {
                switch (data[i]) {
                    case Characters.CarriageReturn:
                        int index = i + 1;
                        if (index < data.Length && data[index] == Characters.LineFeed)
                            i++;
                        int wc = CalculateWhiteSpaceCount(data, i + 1, ref i);
                        WriteLineAndIndent(wc);
                        break;
                    case Characters.LineFeed:
                        wc = CalculateWhiteSpaceCount(data, i + 1, ref i);
                        WriteLineAndIndent(wc);
                        break;
                    default:
                        writer.Write(data[i]);
                        break;
                    }
                }
            }

        public override void WriteText(string text) {
            // Write PCDATA
            if (text == null)
                return;
            int startIndex = IndexOfFirstLineFeed(text);
            if (startIndex < 0)
                startIndex = 0;
            int length = IndexOfEndLineFeed(text);
            if (length < 0)
                length = text.Length;
            for (int i = startIndex; i < length; i++) {
                switch (text[i]) {
                    case Characters.CarriageReturn:
                        int index = i + 1;
                        if (index < text.Length && text[index] == Characters.LineFeed)
                            i++;
                        int wc = CalculateWhiteSpaceCount(text, i + 1, ref i);
                        WriteLineAndIndent(wc);
                        break;
                    case Characters.LineFeed:
                        wc = CalculateWhiteSpaceCount(text, i + 1, ref i);
                        WriteLineAndIndent(wc);
                        break;
                    default:
                        SgmlTextEncoder.EncodeChar(text[i], writer);
                        break;
                    }
                }
            }

        public override void WriteSignificantWhiteSpace(string whitespace) {
            if (whitespace == null)
                return;
            int length = IndexOfEndLineFeed(whitespace);
            if (length < 0)
                length = whitespace.Length;
            for (int i = 0; i < length; i++) {
                switch (whitespace[i]) {
                    case Characters.CarriageReturn:
                        int index = i + 1;
                        if (index < whitespace.Length && whitespace[index] == Characters.LineFeed)
                            i++;
                        writer.WriteLine();
                        break;
                    case Characters.LineFeed:
                        writer.WriteLine();
                        break;
                    case Characters.HorizontalTab:
                        writer.Write(Characters.Whitespace);
                        break;
                    default:
                        writer.Write(whitespace[i]);
                        break;
                    }
                }
            }

        public override void WriteWhiteSpace(string value) {
            return;
            }

        protected override void WriteCommentValue(string value) {
            if (value == null)
                return;
            int length = IndexOfEndLineFeed(value);
            if (length < 0)
                length = value.Length;
            for (int i = 0; i < value.Length; i++) {
                switch (value[i]) {
                    case Characters.CarriageReturn:
                        int index = i + 1;
                        if (index < value.Length && value[index] == Characters.LineFeed)
                            i++;
                        int wc = CalculateWhiteSpaceCount(value, i + 1, ref i);
                        WriteLineAndIndent(wc);
                        break;
                    case Characters.LineFeed:
                        wc = CalculateWhiteSpaceCount(value, i + 1, ref i);
                        WriteLineAndIndent(wc);
                        break;
                    default:
                        writer.Write(value[i]);
                        break;
                    }
                }
            if (length < value.Length)
                WriteLineAndIndent(depth * indent);
            }

        public override void WriteDoctype(SElement doctype) {
            base.WriteDoctype(doctype);
            if (doctype.NextNode != null)
                WriteLineAndIndent(depth * indent);
            }

        public override void WriteElement(SElement element) {
            WriteStartElement(element);
            if (element.HasElements) {
                if (element.Nodes.Count == 1 && element.FirstNode.NodeType == SgmlNodeType.Text)
                    WriteInternal(element.FirstNode);
                else {
                    depth++;
                    foreach (SElement item in element.Nodes) {
                        WriteLineAndIndent(item, item.PreviousNode);
                        WriteInternal(item);
                        }
                    depth--;
                    WriteLineAndIndent(element, element.LastNode);
                    }
                WriteEndElement(element.Name);
                }
            }

        private void WriteLineAndIndent(SElement element, SElement previous) {
            if (element.NodeType == SgmlNodeType.WhiteSpace ||
                element.NodeType == SgmlNodeType.SignificantWhiteSpace)
                return;
            if (element.NodeType == SgmlNodeType.Text ||
                element.NodeType == SgmlNodeType.CData) {
                if (0 <= IndexOfFirstLineFeed(element.Value))
                    return;
                }
            WriteLineAndIndent(depth * indent);
            }

        private int IndexOfFirstLineFeed(string value) {
            int index = 0;
            while (index < value.Length &&
                (value[index] == Characters.HorizontalTab ||
                 value[index] == Characters.Whitespace)) {
                index++;
                }
            if (index == value.Length)
                return -1;
            if (value[index] == Characters.LineFeed)
                return index;
            if (value[index] == Characters.CarriageReturn) {
                if (++index < value.Length && value[index] == Characters.LineFeed)
                    return index;
                return --index;
                }
            else
                return -1;
            }

        private int IndexOfEndLineFeed(string value) {
            int index = value.Length - 1;
            while (index >= 0 &&
                (value[index] == Characters.HorizontalTab ||
                 value[index] == Characters.Whitespace)) {
                index--;
                }
            if (index < 0)
                return -1;
            if (value[index] == Characters.LineFeed) {
                if (--index >= 0 && value[index] == Characters.CarriageReturn)
                    return index;
                return ++index;
                }
            if (value[index] == Characters.CarriageReturn)
                return index;
            else
                return -1;
            }

        private void WriteLineAndIndent(int charsCount) {
            writer.WriteLine();
            for (int i = 0; i < charsCount; i++) {
                writer.Write(Characters.Whitespace);
                }
            }

        private int CalculateWhiteSpaceCount(string text, int startIndex, ref int index) {
            int i = startIndex;
            int count = 0;
            while (i < text.Length) {
                if (text[i] == Characters.HorizontalTab)
                    count += indent * 2;
                else if (text[i] == Characters.Whitespace)
                    count++;
                else
                    break;
                i++;
                index++;
                }
            int adding = depth * indent;
            if (count > 0) {
                float d = 0;
                if (count > adding)
                    d = count / adding;
                else
                    d = adding / count;
                return adding + (int)Math.Round(Math.Abs(count - adding) / d);
                }
            else
                return adding;
            }

        protected override void Dispose(bool disposing) {
            base.Dispose(disposing);
            }
        }
    }
